%% @author Carlos Abalde <carlos.abalde@gmail.com>
%% @doc    Misc utility functions.

%%%----------------------------------------------------------------------
%%%
%%% EMC, Copyright (C) 2009 Carlos Abalde
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------

-module(emc.util.util).

-import(lists).
-import(gen_server).
-import(erlang).
-import(erl_parse).
-import(erl_scan).
-import(file).
-import(io).
-import(io_lib).
-import(random).
-import(crypto).
-import(math).
-import(regexp).
-import(rpc).
-import(inet_udp).
-import(prim_inet).

-include("emc.hrl").

-export([chash/1, in_oc_interval/3]).
-export([keysearch_and_delete/3, keyupdate/4, random_nth/1, rep/2, shake/1]).
-export([trim/1, a2l/1, a2a/1, a2b/1, a2i/1, a2f/1, min/2, max/2]).
-export([timestamp/0, to_miliseconds/1, elapsed_miliseconds/1, elapsed_seconds/1, tag/1]).
-export([term_to_string/1, string_to_term/1, mktemp/0, mkdir/1, mktemp_name/1]).
-export([log/2, floor/1, ceil/1]).
-export([start_link_gen_server/4]).

%%%-----------------------------------------------------------------------------
%%% Math
%%%-----------------------------------------------------------------------------

%%
%%
%%
log(B, X) ->
    math:log10(X)/math:log10(B).

%%
%%
%%
min(A, B) when A < B -> A;
min(_, B) -> B.

%%
%%
%%
max(A, B) when A > B -> A;
max(_, B) -> B.

%%
%%
%%
floor(X) ->
    case trunc(X) of
        Y when Y > X -> Y - 1; 
        Z            -> Z
    end.

%%
%%
%%
ceil(X) ->
    case trunc(X) of
        Y when Y < X -> Y + 1;
        Z            -> Z
    end.

%%%-----------------------------------------------------------------------------
%%% Consistent hashing
%%%-----------------------------------------------------------------------------

%%
%%
%%
chash(Value) ->
    M = 32,
    Hash = binary_to_list(crypto:sha(binary_to_list(term_to_binary(Value)))),
    chash(Hash, length(Hash), 0) rem round(math:pow(2, M)).

chash([], _, Acc) ->
    Acc;
chash([ Value | Rest ], Pos, Acc) ->
    chash(Rest, Pos - 1, Acc + Value * round(math:pow(2, 8 * Pos))).

%%
%% @doc `true' si `X' in (`A', `B'], usando aritmetica modular. Se asume que
%%      `X < M'
%%
in_oc_interval(X, A, B) ->
    ((X > A) and (X =< B)) or ((X > A) and (B < A)) or ((X =< B) and (B < A)) or ( A==B ).

%%%-----------------------------------------------------------------------------
%%% Conversiones
%%%-----------------------------------------------------------------------------

%%
%%
%%
a2l(X) when is_list(X)    -> X;
a2l(X) when is_atom(X)    -> atom_to_list(X);
a2l(X) when is_integer(X) -> integer_to_list(X);
a2l(X) when is_float(X)   -> float_to_list(X);
a2l(X) when is_tuple(X)   -> tuple_to_list(X).    

%%
%%
%%
a2a(X) when is_atom(X)    -> X;
a2a(X) when is_list(X)    -> list_to_atom(X).

%%
%%
%%
a2b(X) when X == "0"      -> false;
a2b(X) when X == "1"      -> true;
a2b(X) when is_list(X)    -> list_to_atom(X);
a2b(X) when is_atom(X)    -> X.

%%
%%
%%
a2i(X) when is_integer(X) -> X;
a2i(X) when is_list(X)    -> list_to_integer(X).

%%
%%
%%
a2f(X) when is_float(X) -> X;
a2f(X) when is_list(X) ->
    case catch list_to_float(X) of
	{'EXIT', _} ->
	    list_to_float(X ++ ".0");
	_Else ->
	    _Else
    end.

%%
%%
%%
string_to_term(StrTerm) ->
    {ok, ItemTokens, _} = erl_scan:string(StrTerm ++ "."),
    erl_parse:parse_term(ItemTokens).

%%
%%
%%
term_to_string(Term) ->
    {ok, lists:flatten(io_lib:format("~w", [Term]))}.  % TODO !!!!!

%%%-----------------------------------------------------------------------------
%%% Lists
%%%-----------------------------------------------------------------------------

%%
%%
%%
keysearch_and_delete(Key, N, TupleList) ->
    case lists:keysearch(Key, N, TupleList) of
        {value, Tuple} -> {value, Tuple, lists:keydelete(Key, N, TupleList)};
        _              -> {false, TupleList}
    end.

%%
%%
%%
keyupdate(Key, N, TupleList, NewTuple) ->
    case lists:keymember(Key, N, TupleList) of
        false -> [NewTuple | TupleList];
        true  -> lists:keyreplace(Key, N, TupleList, NewTuple)
    end.

%%
%%
%%
random_nth(L) ->
    lists:nth(random:uniform(length(L)), L).

%%
%%
%%
shake(L) ->
    shake(L, []).

shake([], Acc) ->
    Acc;
shake(L, Acc) ->
    V = random_nth(L),
    shake(lists:delete(V, L), [V | Acc]).

%%
%%
%%
rep(N, Value) ->
    rep(N, Value, []).

rep(0, _, Acc) ->
    Acc;
rep(N, Value, Acc) ->
    rep(N-1, Value, [Value | Acc]).

%%%-----------------------------------------------------------------------------
%%% Misc
%%%-----------------------------------------------------------------------------

%%
%%
%%
start_link_gen_server({_, ServerName}=SN, Module, Args, Options) ->
    case gen_server:start_link(SN, Module, Args, Options) of
        {ok, Pid} ->
	    catch unregister(ServerName),
	    register(ServerName, Pid),
	    {ok, Pid};
        _Else ->
            _Else
    end.

%%
%%
%%
trim(List) when is_list(List) ->
    List1 = skip_spaces(List),
    List2 = skip_spaces(lists:reverse(List1)),
    lists:reverse(List2).

%%
skip_spaces([H|T]) when H == $  ->
    skip_spaces(T);

skip_spaces(List) ->
    List.

%%
%%
%%
timestamp() ->
    {S1, S2, S3} = erlang:now(),
    S1*1000000000000 + S2*1000000 + S3.

%%
%%
%%
to_miliseconds({S1, S2, S3}) ->
    S1*1000000000000 + S2*1000000 + S3.

%%
%%
%%
elapsed_miliseconds({S1, S2, S3}) ->
    elapsed_miliseconds(S1*1000000000000 + S2*1000000 + S3);

elapsed_miliseconds(MicroSecs) ->
    {S1, S2, S3} = erlang:now(),
    round(((S1*1000000000000 + S2*1000000 + S3) - MicroSecs) / 1000).

%%
%%
%%
elapsed_seconds({S1, S2, S3}) ->
    elapsed_seconds(S1*1000000000000 + S2*1000000 + S3);

elapsed_seconds(MicroSecs) ->
    {S1, S2, S3} = erlang:now(),
    round(((S1*1000000000000 + S2*1000000 + S3) - MicroSecs)/1000000).

%%
%%
%%
tag(X) ->
    {S1, S2, S3} = erlang:now(),
    a2a(a2l(X) ++ "_" ++ a2l(S1) ++ a2l(S2) ++ a2l(S3)).

%%
%%
%%
mktemp_name(Prefix) ->
    {A, B, C} = now(),
    {ok, "/tmp/" ++ Prefix ++ integer_to_list(A) ++ integer_to_list(B) ++ integer_to_list(C)}.

%%
%%
%%
mktemp() ->
    {ok, TmpFile} = mktemp_name(""),
    {ok, TmpIoDevice} = file:open(TmpFile, [write, read, raw]),
    {ok, TmpFile, TmpIoDevice}.

%%
%%
%%
mkdir(DirName) ->
    {ok, ["" | Dirs]} = regexp:split(DirName, "/"),
    lists:foldl(fun(Dir, AccIn) ->
                    AccOut = AccIn ++ Dir ++ "/",
                    catch file:make_dir(AccOut),
                    AccOut
                end, "/", Dirs),
    ok.
